上一篇我們介紹HttpModule & HttpHandler對於
今天正式進入.Net CLR處理HTTP請求的世界.
先附上Asp.net執行請求流程圖.

現在開始講解藍色區塊.
查看原始碼好站 Reference Source
World Wide Web Publishing Service(簡稱W3SVC)是一個Window Service.
W3SVC在SvcHost.exe這個應用程式上被執行.
W3SVC主要功能
當檢測到某個HTTP Request後,先根據一個註冊表判斷請求的副檔名是否是靜態資源(比如.html,.img,.txt,.xml...)
如果是則直接將文件內容以HTTP Response的形式返回。
如果是動態資源(比如.aspx,asp,php等等),則通過副檔名從IIS的Script Map找到相應ISAPI.dll
前面說到透過W3SVC服務
System.Web.Hosting.IISAPIRuntime這個介面是一個基於COM的Interface,ASP.NET ISAPI可以通過COM的方式調用實現該Interface的Class物件的ProcessRequest方法,從非託管環境進入了託管的環境。
[ComImport, Guid("08a2c56f-7c16-41c1-a8be-432917a1a2d1"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IISAPIRuntime {
void StartProcessing();
void StopProcessing();
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest(
[In]
IntPtr ecb,
[In, MarshalAs(UnmanagedType.I4)]
int useProcessModel);
void DoGCCollect();
}
所以
IISAPIRuntime.ProcessRequest是我們探討原始碼起始點.
一開始會先呼叫IsapiRunTime的ProcessRequest方法來執行此次請求.
在CreateWorkerRequest會依據不同IIS版本建立不同ISAPIWorkerRequest物件,之後在呼叫Initialize方法把HTTP請求內容初次填入這個對象.
public int ProcessRequest(IntPtr ecb, int iWRType) {
IntPtr pHttpCompletion = IntPtr.Zero;
if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) {
pHttpCompletion = ecb;
ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion);
}
ISAPIWorkerRequest wr = null;
try {
bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP);
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
wr.Initialize();
String wrPath = wr.GetAppPathTranslated();
String adPath = HttpRuntime.AppDomainAppPathInternal;
if (adPath == null ||
StringUtil.EqualsIgnoreCase(wrPath, adPath)) {
HttpRuntime.ProcessRequestNoDemand(wr);
return 0;
}
else {
// need to restart app domain
HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged,
SR.GetString(SR.Hosting_Phys_Path_Changed,
adPath,
wrPath));
return 1;
}
}
catch(Exception e) {
try {
WebBaseEvent.RaiseRuntimeError(e, this);
} catch {}
if (wr != null && wr.Ecb == IntPtr.Zero) {
if (pHttpCompletion != IntPtr.Zero) {
UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);
}
if (e is ThreadAbortException) {
Thread.ResetAbort();
}
return 0;
}
throw;
}
}
這段程式碼有幾個重點:
WorkerRequest物件中,方便日後使用.wr.Initialize()初始化WorkerRequest物件HttpRuntime.ProcessRequestNoDemand方法並把剛剛初始化的WorkerRequest物件當作參數傳入.其中參數ecb(Execution Control Block)是一個Unmanaged Pointer
ISAPIRuntime不能直接調用ASP.NET ISAPI,所以通過一個ecb物件指標,ecb實現ISAPI和ISAPIRutime之間溝通.
先來看看剛剛呼叫的HttpRuntime.ProcessRequestNoDemand方法.
這裡需要注意兩個重點.
判斷目前執行程序池是否已經超過負荷,如果是會把wr物件指向null
if (rq != null)
wr = rq.GetRequestToExecute(wr);
如果wr!=null(代表還有資源可以執行請求)就呼叫ProcessRequestNow方法會繼續呼叫ProcessRequestInternal方法.
internal static void ProcessRequestNoDemand(HttpWorkerRequest wr) {
RequestQueue rq = _theRuntime._requestQueue;
wr.UpdateInitialCounters();
if (rq != null) // could be null before first request
wr = rq.GetRequestToExecute(wr);
if (wr != null) {
CalculateWaitTimeAndUpdatePerfCounter(wr);
wr.ResetStartTime();
ProcessRequestNow(wr);
}
}
internal static void ProcessRequestNow(HttpWorkerRequest wr) {
_theRuntime.ProcessRequestInternal(wr);
}
在HttpRuntime很重要的方法之一是ProcessRequestInternal
下面程式碼,我把
ProcessRequestInternal方法中註解移除且只貼出我覺得重要的程式碼
此方法有做幾個事情:
wr.SendStatus(503, "Server Too Busy");
HttpWorkerRequest物件封裝我們常常使用HttpContext
HttpApplicationFactory.GetApplicationInstance返回一個IHttpHandler物件IHttpHandler物件支援異步請求優先執行,不然就執行同步請求.上面第3,4點最為重要,因為我們就可以很清楚了解到為什麼最後都會找到一個繼承IHttpHandler介面的物件來執行ProcessRequest方法.
因為Asp.net在HttpRunTime程式碼中倚賴一個IHttpHandler介面抽象才造就具有彈性的系統架構.
private void ProcessRequestInternal(HttpWorkerRequest wr) {
HttpContext context;
try {
//封裝我們常常使用`HttpContext`
context = new HttpContext(wr, false /* initResponseWriter */);
}
catch {
try {
wr.SendStatus(400, "Bad Request");
wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8");
byte[] body = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
wr.SendResponseFromMemory(body, body.Length);
wr.FlushResponse(true);
wr.EndOfRequest();
return;
} finally {
Interlocked.Decrement(ref _activeRequestCount);
}
}
try {
try {
EnsureFirstRequestInit(context);
}
catch {
if (!context.Request.IsDebuggingRequest) {
throw;
}
}
context.Response.InitResponseWriter();
IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);
if (app == null)
throw new HttpException(SR.GetString(SR.Unable_create_app_object));
if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start");
//如果返回的IHttpHandler物件支援異步請求優先執行,不然就執行同步請求.
if (app is IHttpAsyncHandler) {
// asynchronous handler
IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app;
context.AsyncAppHandler = asyncHandler;
asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);
}
else {
// synchronous handler
app.ProcessRequest(context);
FinishRequest(context.WorkerRequest, context, null);
}
}
catch (Exception e) {
context.Response.InitResponseWriter();
FinishRequest(wr, context, e);
}
}
下面此這個方法執行時兩個小重點.
ProcessRequestInternal方法初始化我們常用HttpContext物件,把Http內容封裝到這個類別中.
如果返回
IHttpHandler物件支援異步請求優先執行,不然就執行同步請求.
今天我們學到
ISAPIRunTime.ProcessRequest方法
WorkerRequest物件把Http內容封裝到裡面,並呼叫HttpRuntime.ProcessRequestNoDemand方法.HttpRuntime.ProcessRequestNoDemand方法
HttpContext並初始化內容資料HttpApplicationFactory.GetApplicationInstance取得IHttpHanlder物件IHttpHanlder ProcessRequest方法下篇我們會來好好介紹HttpApplicationFactory這個工廠到底如何返回IHttpHanlder物件.
iThome鐵人賽